import { URI } from "vscode-uri";
import * as lsp from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";

export interface TextDocuments extends lsp.Disposable {
    readonly onTextDocumentOpened: lsp.Event<TextDocument>;
    readonly onTextDocumentChanged: lsp.Event<TextDocument>;
    readonly onTextDocumentClosed: lsp.Event<TextDocument>;
    get(fsPath: string): TextDocument | undefined;
    open(fsPath: string, document: lsp.TextDocumentItem): boolean;
    update(fsPath: string, changes: lsp.TextDocumentContentChangeEvent[]): TextDocument | undefined;
    close(fsPath: string): TextDocument | undefined;
    toUri(fsPath: string): URI;
    forEach(callback: (e: TextDocument) => any, thisArg?: any): void;
}

export function createTextDocuments(): TextDocuments {

    const openEmitter = new lsp.Emitter<TextDocument>();
    const changedEmitter = new lsp.Emitter<TextDocument>();
    const closeEmitter = new lsp.Emitter<TextDocument>();

    // 使用小写的文件路径作为键保存
    const documents = new Map<string, TextDocument>();
    const files: string[] = [];

    return {
        onTextDocumentOpened: openEmitter.event,
        onTextDocumentChanged: changedEmitter.event,
        onTextDocumentClosed: closeEmitter.event,
        get,
        open,
        update,
        close,
        toUri,
        forEach,
        dispose() {
            openEmitter.dispose();
            closeEmitter.dispose();
            documents.clear();
        }
    };

    function get(fsPath: string): TextDocument | undefined {
        let result: TextDocument | undefined;
        let key = fsPath.toLowerCase();
        result = documents.get(key);
        if (!result) {
            return undefined;
        }
        if (files[0] !== fsPath) {
            files.splice(files.indexOf(fsPath), 1);
            files.unshift(fsPath);
        }
        return result;
    }

    function open(fsPath: string, document: lsp.TextDocumentItem): boolean {
        let lowerPath = fsPath.toLowerCase();
        if (documents.has(lowerPath)) {
            return false;
        }
        const newDocument = TextDocument.create(document.uri, document.languageId, document.version, document.text);
        documents.set(lowerPath, newDocument);
        files.unshift(fsPath);
        openEmitter.fire(newDocument);
        return true;
    }

    function close(fsPath: string): TextDocument | undefined {
        let lowerPath = fsPath.toLowerCase();
        let document = documents.get(lowerPath);
        if (!document) {
            return undefined;
        }
        documents.delete(lowerPath);
        files.splice(files.indexOf(fsPath), 1);
        closeEmitter.fire(document);
        return document;
    }

    function update(fsPath: string, changes: lsp.TextDocumentContentChangeEvent[]): TextDocument | undefined {
        let document = documents.get(fsPath.toLowerCase());
        if (document) {
            TextDocument.update(document, changes, document.version + 1);
            changedEmitter.fire(document);
        }
        return document;
    }

    function toUri(fsPath: string): URI {
        let document = documents.get(fsPath.toLowerCase());
        if (document) {
            return URI.parse(document.uri);
        }
        return URI.file(fsPath);
    }

    function forEach(callback: (e: TextDocument) => any, thisArg?: any): void {
        for (const item of documents.values()) {
            callback.call(thisArg, item);
        }
    }

}
